package top.v5it.japi.plus.sdk;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.util.StringValueResolver;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author zhanpu
 * @date 2022/6/24
 */
public class JapiApiUtil {

    private static final String PREFIX = "{";
    private static final String SUFFIX = "}";
    private static final String PREFIX_DOLLAR = "${";
    private static final Pattern PARAMETER_ANALYSIS = Pattern.compile("(\\{([\\w_]+\\d*)})*");
    private static final Predicate<String> EXPRESSION = v -> v.contains(PREFIX) && v.contains(SUFFIX);

    /**
     * 获取JapiHttpClient
     */
    private static final Function<JapiApi, JapiHttpClient> FUNC_CLIENT = v ->
            Optional.of(v)
                    .map(JapiApi::beanName)
                    .filter(StrUtil::isNotEmpty)
                    .map(SpringUtil::getBean)
                    .map(t -> (JapiHttpClient) t)
                    .orElse(SpringUtil.getBean(JapiHttpClient.class));

    /**
     * 获取path
     */
    private static final BiFunction<StringValueResolver, JapiApi, String> FUNC_PATH = (t, u) ->
            Optional.of(u)
                    .map(JapiApi::path)
                    .filter(v -> StrUtil.contains(v, PREFIX_DOLLAR) && StrUtil.contains(v, SUFFIX))
                    .map(t::resolveStringValue)
                    .orElse(u.path());

    /**
     * GET方法请求
     *
     * @param stringValueResolver
     * @param japiApi
     * @param args
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Object get(StringValueResolver stringValueResolver, JapiApi japiApi, Object[] args) {
        JapiHttpClient japiHttpClient = FUNC_CLIENT.apply(japiApi);
        String path = FUNC_PATH.apply(stringValueResolver, japiApi);
        int length = ArrayUtil.length(args);
        boolean match = EXPRESSION.negate().test(path);

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, resultType = TestExampleDTO.class, path = "/v1/example")
         *     public TestExampleDTO test() {
         *         return null;
         *     }
         * </pre>
         */
        if (ArrayUtil.isEmpty(args) && match) {
            return japiHttpClient.get(path, null, japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, resultType = TestExampleDTO.class, path = "/v1/example")
         *     public TestExampleDTO test(Map<String, Object> params) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 1
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            return japiHttpClient.get(path, (Map<String, Object>) args[0], japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, path = "/v1/example")
         *     public List<TestExampleDTO> test(TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 1
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            return japiHttpClient.get(path, null, (TypeReference<?>) args[0]);
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, path = "/v1/example")
         *     public List<TestExampleDTO> test(Map<String, Object> params, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 2
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[0]))
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[1]))) {
            return japiHttpClient.get(path, (Map<String, Object>) args[0], (TypeReference<?>) args[1]);
        }

        // 路径上有表达式，解析表达式填充参数
        if (Objects.nonNull(args[0])
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            // 参数不为空，并且第1个参数就是map，根据表达式名称来填充参数
            Map<String, Object> params = ((Map<String, Object>) args[0]);
            path = fillPathByMap(params, path);

            /**
             * <pre>
             *     @JapiApi(method = Method.GET, resultType = TestExampleDTO.class, path = "/v1/example/{id}")
             *     public TestExampleDTO test(Map<String, Object> params) {
             *         return null;
             *     }
             * </pre>
             */
            if (length == 1) {
                return japiHttpClient.get(path, params, japiApi.resultType());
            }

            /**
             * <pre>
             *     @JapiApi(method = Method.GET, path = "/v1/example/{id}")
             *     public List<TestExampleDTO> test(Map<String, Object> params, TypeReference<List<TestExampleDTO>> typeReference) {
             *         return null;
             *     }
             * </pre>
             */
            if (length == 2
                    && Objects.nonNull(args[1])
                    && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[1]))) {
                return japiHttpClient.get(path, params, (TypeReference<?>) args[1]);
            }

        }

        // 非map参数，此时参数必须为基本类型或者包装类型参数
        // 解析path，获取参数名称
        Triple2<Integer, String> triple2 = fillPathByBasicType(args, path);
        path = triple2.getTwo();

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id) {
         *         return null;
         *     }
         * </pre>
         */
        if (Arrays.stream(args).allMatch(v -> ClassUtil.isBasicType(ClassUtil.getClass(v)) || CharSequence.class.isAssignableFrom(ClassUtil.getClass(v)))) {
            return japiHttpClient.get(path, null, japiApi.resultType());
        }

        int count = triple2.getOne();

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 1
                && Objects.nonNull(args[count])
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[count]))) {
            return japiHttpClient.get(path, null, (TypeReference<?>) args[count]);
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, resultType = TestExampleDTO.class, path = "/v1/example/{id}")
         *     public TestExampleDTO test(Integer id, Map<String, Object> params) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 1
                && Objects.nonNull(args[count])
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[count]))) {
            return japiHttpClient.get(path, (Map<String, Object>) args[count], japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.GET, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id, Map<String, Object> params, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 2
                && Objects.nonNull(args[count])
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[count]))
                && Objects.nonNull(args[count + 1])
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[count + 1]))) {
            return japiHttpClient.get(path, (Map<String, Object>) args[count], (TypeReference<?>) args[count + 1]);
        }

        throw new IllegalArgumentException("请求参数错误");
    }

    /**
     * POST方法请求
     *
     * @param stringValueResolver
     * @param japiApi
     * @param args
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Object post(StringValueResolver stringValueResolver, JapiApi japiApi, Object[] args) {
        JapiHttpClient japiHttpClient = FUNC_CLIENT.apply(japiApi);
        String path = FUNC_PATH.apply(stringValueResolver, japiApi);
        int length = ArrayUtil.length(args);
        boolean match = EXPRESSION.negate().test(path);

        /**
         * <pre>
         *     @JapiApi(method = Method.POST, resultType = TestExampleDTO.class, path = "/v1/example")
         *     public TestExampleDTO test() {
         *         return null;
         *     }
         * </pre>
         */
        if (ArrayUtil.isEmpty(args) && match) {
            return japiHttpClient.post(path, null, japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.POST, path = "/v1/example")
         *     public List<TestExampleDTO> test(TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 1
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            return japiHttpClient.post(path, null, (TypeReference<?>) args[0]);
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.POST, resultType = TestExampleDTO.class, path = "/v1/example")
         *     public TestExampleDTO test(TestExampleDTO dto) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 1) {
            return japiHttpClient.post(path, args[0], japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.POST, path = "/v1/example")
         *     public List<TestExampleDTO> test(TestExampleDTO dto, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 2
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[1]))) {
            return japiHttpClient.post(path, args[0], (TypeReference<?>) args[1]);
        }

        // 路径上有表达式，解析表达式填充参数
        if (Objects.nonNull(args[0])
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            // 参数不为空，并且第1个参数就是map，根据表达式名称来填充参数
            path = fillPathByMap(((Map<String, Object>) args[0]), path);

            /**
             * <pre>
             *     @JapiApi(method = Method.POST, path = "/v1/example/{id}")
             *     public List<TestExampleDTO> test(Map<String, Object> params, TypeReference<List<TestExampleDTO>> typeReference) {
             *         return null;
             *     }
             * </pre>
             * 或
             * <pre>
             *     @JapiApi(method = Method.POST, resultType = TestExampleDTO.class, path = "/v1/example/{id}")
             *     public TestExampleDTO test(Map<String, Object> params, TestExampleDTO dto) {
             *         return null;
             *     }
             * </pre>
             */
            if (length == 2) {
                return Objects.nonNull(args[1])
                        && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[1]))

                        ? japiHttpClient.post(path, null, (TypeReference<?>) args[1])
                        : japiHttpClient.post(path, args[1], japiApi.resultType());
            }

            /**
             * <pre>
             *     @JapiApi(method = Method.POST, path = "/v1/example/{id}")
             *     public List<TestExampleDTO> test(Map<String, Object> params, TestExampleDTO dto, TypeReference<List<TestExampleDTO>> typeReference) {
             *         return null;
             *     }
             * </pre>
             */
            if (length == 3
                    && Objects.nonNull(args[2])
                    && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[2]))) {
                return japiHttpClient.post(path, args[1], (TypeReference<?>) args[2]);
            }
        }

        // 非map参数，此时参数必须为基本类型或者包装类型参数
        // 解析path，获取参数名称
        Triple2<Integer, String> triple2 = fillPathByBasicType(args, path);
        int count = triple2.getOne();

        /**
         * <pre>
         *     @JapiApi(method = Method.POST, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         * 或
         * <pre>
         *     @JapiApi(method = Method.POST, resultType = TestExampleDTO.class, path = "/v1/example/{id}")
         *     public TestExampleDTO test(Integer id, TestExampleDTO dto) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 1) {
            return Objects.nonNull(args[count])
                    && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[count]))

                    ? japiHttpClient.post(path, null, (TypeReference<?>) args[count])
                    : japiHttpClient.post(path, args[count], japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.POST, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id, TestExampleDTO dto, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 2
                && Objects.nonNull(args[count + 1])
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[count + 1]))) {
            return japiHttpClient.post(path, args[count], (TypeReference<?>) args[count + 1]);
        }

        throw new IllegalArgumentException("请求参数错误");
    }

    /**
     * PUT方法请求
     *
     * @param stringValueResolver
     * @param japiApi
     * @param args
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Object put(StringValueResolver stringValueResolver, JapiApi japiApi, Object[] args) {
        JapiHttpClient japiHttpClient = FUNC_CLIENT.apply(japiApi);
        String path = FUNC_PATH.apply(stringValueResolver, japiApi);
        int length = ArrayUtil.length(args);
        boolean match = EXPRESSION.negate().test(path);

        /**
         * <pre>
         *     @JapiApi(method = Method.PUT, resultType = TestExampleDTO.class, path = "/v1/example")
         *     public TestExampleDTO test() {
         *         return null;
         *     }
         * </pre>
         */
        if (ArrayUtil.isEmpty(args) && match) {
            return japiHttpClient.put(path, null, japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.PUT, path = "/v1/example")
         *     public List<TestExampleDTO> test(TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 1
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            return japiHttpClient.put(path, null, (TypeReference<?>) args[0]);
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.PUT, resultType = TestExampleDTO.class, path = "/v1/example")
         *     public TestExampleDTO test(TestExampleDTO dto) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 1) {
            return japiHttpClient.put(path, args[0], japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.PUT, path = "/v1/example")
         *     public List<TestExampleDTO> test(TestExampleDTO dto, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (match && length == 2
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[1]))) {
            return japiHttpClient.put(path, args[0], (TypeReference<?>) args[1]);
        }

        // 路径上有表达式，解析表达式填充参数
        if (Objects.nonNull(args[0])
                && Map.class.isAssignableFrom(ClassUtil.getClass(args[0]))) {
            // 参数不为空，并且第1个参数就是map，根据表达式名称来填充参数
            path = fillPathByMap(((Map<String, Object>) args[0]), path);

            /**
             * <pre>
             *     @JapiApi(method = Method.PUT, path = "/v1/example/{id}")
             *     public List<TestExampleDTO> test(Map<String, Object> params, TypeReference<List<TestExampleDTO>> typeReference) {
             *         return null;
             *     }
             * </pre>
             * 或
             * <pre>
             *     @JapiApi(method = Method.PUT, resultType = TestExampleDTO.class, path = "/v1/example/{id}")
             *     public TestExampleDTO test(Map<String, Object> params, TestExampleDTO dto) {
             *         return null;
             *     }
             * </pre>
             */
            if (length == 2) {
                return Objects.nonNull(args[1])
                        && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[1]))

                        ? japiHttpClient.put(path, null, (TypeReference<?>) args[1])
                        : japiHttpClient.put(path, args[1], japiApi.resultType());
            }

            /**
             * <pre>
             *     @JapiApi(method = Method.PUT, path = "/v1/example/{id}")
             *     public List<TestExampleDTO> test(Map<String, Object> params, TestExampleDTO dto, TypeReference<List<TestExampleDTO>> typeReference) {
             *         return null;
             *     }
             * </pre>
             */
            if (length == 3
                    && Objects.nonNull(args[2])
                    && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[2]))) {
                return japiHttpClient.post(path, args[1], (TypeReference<?>) args[2]);
            }

        }

        // 非map参数，此时参数必须为基本类型或者包装类型参数
        // 解析path，获取参数名称
        Triple2<Integer, String> triple2 = fillPathByBasicType(args, path);
        int count = triple2.getOne();

        /**
         * <pre>
         *     @JapiApi(method = Method.PUT, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         * 或
         * <pre>
         *     @JapiApi(method = Method.PUT, resultType = TestExampleDTO.class, path = "/v1/example/{id}")
         *     public TestExampleDTO test(Integer id, TestExampleDTO dto) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 1) {
            return Objects.nonNull(args[count])
                    && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[count]))

                    ? japiHttpClient.put(path, null, (TypeReference<?>) args[count])
                    : japiHttpClient.put(path, args[count], japiApi.resultType());
        }

        /**
         * <pre>
         *     @JapiApi(method = Method.PUT, path = "/v1/example/{id}")
         *     public List<TestExampleDTO> test(Integer id, TestExampleDTO dto, TypeReference<List<TestExampleDTO>> typeReference) {
         *         return null;
         *     }
         * </pre>
         */
        if (length == count + 2
                && Objects.nonNull(args[count + 1])
                && TypeReference.class.isAssignableFrom(ClassUtil.getClass(args[count + 1]))) {
            return japiHttpClient.put(path, args[count], (TypeReference<?>) args[count + 1]);
        }

        throw new IllegalArgumentException("请求参数错误");
    }

    private static String fillPathByMap(Map<String, Object> params, String path) {

        for (Map.Entry<String, Object> next : params.entrySet()) {
            path = path.replace(PREFIX + next.getKey() + SUFFIX, Objects.toString(next.getValue()));
            params.remove(next.getKey());
        }

        Assert.isFalse(EXPRESSION.test(path), () -> new IllegalArgumentException("请求参数缺失"));

        return path;
    }

    private static Triple2<Integer, String> fillPathByBasicType(Object[] args, String path) {
        int length = ArrayUtil.length(args);
        List<String> pathParameterKey =
                ReUtil.findAll(PARAMETER_ANALYSIS, path, 0)
                        .stream()
                        .filter(StrUtil::isNotBlank)
                        .collect(Collectors.toList());

        Assert.isFalse(CollUtil.isEmpty(pathParameterKey), () -> new IllegalArgumentException("请求路径错误"));
        boolean match = CollUtil.isNotEmpty(pathParameterKey) && pathParameterKey.size() > length;
        Assert.isFalse(match, () -> new IllegalArgumentException("请求参数缺失"));

        int count = 0;
        for (String next : pathParameterKey) {
            path = path.replace(next, Objects.toString(args[count++]));
        }

        Assert.isFalse(EXPRESSION.test(path), () -> new IllegalArgumentException("请求参数缺失"));

        return new Triple2<Integer, String>().setOne(count).setTwo(path);
    }

}
